package com.katesoft.scale4j.persistent.spring;

import static com.katesoft.scale4j.persistent.spring.HibernateSessionUtility.hibernateProperties;
import static com.katesoft.scale4j.persistent.spring.HibernateSessionUtility.performSchemaUpdate;
import static com.katesoft.scale4j.persistent.spring.HibernateSessionUtility.storeSchemaCreateDeleteScripts;

import java.util.Map;
import java.util.Properties;
import java.util.concurrent.locks.Lock;

import org.hibernate.HibernateException;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;

import com.katesoft.scale4j.common.lock.IDistributedLockProvider;
import com.katesoft.scale4j.log.LogFactory;
import com.katesoft.scale4j.log.Logger;
import com.katesoft.scale4j.persistent.hibernate.EventListeners;
import com.katesoft.scale4j.persistent.hibernate.StoreUpdateScriptsConfiguration;

/**
 * extended session factory bean.
 * <p/>
 * This class will create drop-create schema script as well as update schema script(if necessary)
 * and drop schema script after building session factory.
 * 
 * @author kate2007
 */
public class LocalAnnotationSessionFactory extends AnnotationSessionFactoryBean implements
         ILocalHibernateSessionBuilder {
   private final Logger log = LogFactory.getLogger(getClass());
   private EventListeners eventListeners;
   private IDistributedLockProvider lockProvider;
   private DatabasePopulator databasePopulator;
   private StoreUpdateScriptsConfiguration updateScriptsConfiguration = new StoreUpdateScriptsConfiguration();

   public LocalAnnotationSessionFactory() {
      eventListeners = new EventListeners(null);
      super.setEventListeners(eventListeners.listeners());
      setNamingStrategy(new org.hibernate.cfg.ImprovedNamingStrategy());
   }

   @Override
   public void setEventListeners(final Map<String, Object> listeners) {
      this.eventListeners = new EventListeners(listeners);
      Map<String, Object> map = this.eventListeners.listeners();
      log.debug("setting client's(wrapped with internal) event_listeners = %s", map);
      super.setEventListeners(map);
   }

   @Override
   protected void afterSessionFactoryCreation() throws Exception {
      storeSchemaCreateDeleteScripts(getConfiguration(), updateScriptsConfiguration);
      super.afterSessionFactoryCreation();
      if (eventListeners != null) {
         eventListeners.initialize(getConfiguration());
      }
   }

   @Override
   public void updateDatabaseSchema() throws DataAccessException {
      if (lockProvider != null) {
         log.info("using lockProvider %s for db schema update", lockProvider);
         Lock lock = lockProvider.lockFor(RTTP_DB_UPDATE_LOCK);
         try {
            if (lock.tryLock()) {
               try {
                  if (!lockProvider.isWorkDone()) {
                     performSchemaUpdate(getDataSource(), getConfiguration(),
                              updateScriptsConfiguration, databasePopulator);
                     lockProvider.markWorkDone();
                     log.info("db schema update done using lock %s", lock);
                  }
               } finally {
                  lock.unlock();
               }
            }
         } catch (DataAccessException e) {
            logger.error(e);
            throw e;
         }
      } else {
         performSchemaUpdate(getDataSource(), getConfiguration(), updateScriptsConfiguration,
                  databasePopulator);
      }
   }

   @Override
   public void setUpdateScriptsConfiguration(StoreUpdateScriptsConfiguration cfg) {
      this.updateScriptsConfiguration = cfg;
   }

   @Override
   public StoreUpdateScriptsConfiguration getUpdateScriptsConfiguration() {
      return updateScriptsConfiguration;
   }

   @Override
   public void setLockProvider(IDistributedLockProvider lockProvider) {
      this.lockProvider = lockProvider;
   }

   @Override
   public void setDatabasePopulator(DatabasePopulator databasePopulator) {
      this.databasePopulator = databasePopulator;
   }

   @Override
   public void setHibernateProperties(Properties hibernateProperties) {
      super.setHibernateProperties(hibernateProperties(hibernateProperties));
   }

   @Override
   public void destroy() throws HibernateException {
      try {
         eventListeners.cleanup();
      } finally {
         super.destroy();
      }
   }
}
